use super::{
    cbase::CBase, err, err_value, get_db, json, ok, throw_err, Base, Client, ECode, MapSv, Resp,
    VBase, VMap, VSend, Value,
};
use chrono::prelude::*;
use std::collections::HashMap;

pub struct Write<'a> {
    client: &'a Client,
}

impl Base for Write<'_> {}

impl CBase for Write<'_> {}

impl<'a> Write<'a> {
    pub async fn do_cmd(&self, cmd: &str, data: &MapSv, tx: VSend) -> Resp {
        match cmd {
            "chunk" => self.do_chunk(data, false, tx).await,
            "gchunk" => self.do_chunk(data, true, tx).await,
            "vchunk" => self.do_vchunk(data, tx).await,
            _ => throw_err!(ECode::NoServe, "no serve"),
        }
    }

    fn get_que(&self, data: &MapSv) -> bool {
        match data.k_bool("que") {
            Some(v) => v,
            None => false,
        }
    }

    fn chunk_base(&self, para: &MapSv, ck: bool) -> Result<Vec<String>, Value> {
        let tab = para.ck_str_trim("tab")?;
        let data = para.ck_array("data")?;
        let rmempty = match para.k_bool("rmempty") {
            Some(v) => v,
            None => false,
        };

        let line = para.ck_str_trim("line");
        if ck {
            if let Err(e) = line {
                throw_err!(ECode::Para, e);
            }
        }

        let mut ret = Vec::new();

        for (i, it) in data.iter().enumerate() {
            let it = it.ckobj()?;

            let mut tag = HashMap::new();

            if let Ok(v) = line {
                tag.insert("line", v);
            }

            if let Some(psn) = it.k_str("psn") {
                tag.insert("psn", psn);
            }

            if let Some(wsn) = it.k_str("wsn") {
                tag.insert("wsn", wsn);
            }

            if let Some(key) = it.k_obj("k") {
                for (k, v) in key.iter() {
                    let k = k.trim();
                    if k.is_empty() {
                        continue;
                    }

                    if ck {
                        if k == "psn" || k == "wsn" || k == "line" {
                            throw_err!(ECode::Para, format!("tag cannot be duplicates: {k}"));
                        }
                    }

                    if let Some(v) = v.kstr() {
                        tag.insert(k, v);
                    }
                }
            }

            let field = it.ck_obj("v")?;

            let (cmd, ti) = match it.k_str("time") {
                Some(v) => {
                    let tm = self.utc_time(v)?;
                    let tm = match tm.timestamp_nanos_opt() {
                        Some(v) => v,
                        None => tm.timestamp_micros() * 1000,
                    };

                    (
                        json!({
                            "time": tm.to_string(),
                            "tab": tab,
                            "tag": tag,
                            "field": field,
                        }),
                        false,
                    )
                }
                None => (
                    json!({
                        "tab": tab,
                        "tag": tag,
                        "field": field,
                    }),
                    true,
                ),
            };

            let q = self.tag_field(&cmd, rmempty, i, ti)?;

            ret.push(q);
        } // for

        Ok(ret)
    }

    async fn do_vchunk(&self, para: &MapSv, tx: VSend) -> Resp {
        let ck = match para.k_bool("ck") {
            Some(v) => v,
            None => true,
        };

        let data = para.ck_array("v")?;

        let mut ret = Vec::new();

        for it in data.iter() {
            let it = it.ckobj()?;

            let mut tmp = self.chunk_base(it, ck)?;

            ret.append(&mut tmp);
        }

        let now: DateTime<Local> = Local::now();
        let mut time = match now.timestamp_nanos_opt() {
            Some(v) => v,
            None => now.timestamp_micros() * 1000,
        };

        for it in ret.iter_mut() {
            *it = format!("{} {}", it, time);
            time += 1;
        }

        let db = get_db(para);
        let que = self.get_que(para);

        let q = ret.join("\n");

        if que {
            let cmd = json!({
                "db": db,
                "q": q
            });

            let _ = tx.send(cmd).await;
        } else {
            self.client.send_write(&q, &db).await?;
        }

        ok!()
    }

    async fn do_chunk(&self, para: &MapSv, group: bool, tx: VSend) -> Resp {
        let ck = match para.k_bool("ck") {
            Some(v) => v,
            None => true,
        };

        let ret = self.chunk_base(para, ck)?;
        let db = get_db(para);
        let que = self.get_que(para);

        if que {
            if group {
                let cmd = json!({
                    "db": db,
                    "q": ret.join("\n"),
                });

                let _ = tx.send(cmd).await;
            } else {
                for q in ret.iter() {
                    let cmd = json!({
                        "db": db,
                        "q": q
                    });

                    let _ = tx.send(cmd).await;
                }
            }
        } else {
            if group {
                let q = ret.join("\n");
                self.client.send_write(&q, &db).await?;
            } else {
                for q in ret.iter() {
                    self.client.send_write(&q, &db).await?;
                }
            }
        }

        ok!()
    }

    pub fn new(client: &'a Client) -> Self {
        Write { client }
    }
}
